package com.million.project.service.serviceimpl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.million.project.Neo4j.controller.neo4jController;
import com.million.project.Neo4j.entity.ShortestPath;
import com.million.project.mapper.MapRouteEdgeMapper;
import com.million.project.mapper.MapRouteMapper;
import com.million.project.mapper.MapRouteNodeMapper;
import com.million.project.pojo.MapRoute;
import com.million.project.pojo.MapRouteEdge;
import com.million.project.pojo.MapRouteNode;
import com.million.project.service.MapRouteService;
import com.million.project.utils.DragonResult;
import com.million.project.vo.mapRouteEdgeVO;
import com.million.project.vo.mapRouteNodeVO;
import com.million.project.vo.mapRouteVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.*;

@Service // 注入
public class MapRouteServiceImpl implements MapRouteService {

    @Autowired
    MapRouteMapper mapRouteMapper;

    @Autowired
    MapRouteNodeMapper mapRouteNodeMapper;

    @Autowired
    MapRouteEdgeMapper mapRouteEdgeMapper;

    @Autowired
    neo4jController neo4jController;
    private static final Base64.Decoder decoder = Base64.getDecoder(); // base64解码器
    private static final Base64.Encoder encoder = Base64.getEncoder(); // base64编码器
    public static ObjectMapper objectMapper = new ObjectMapper(); // Jackson中的一个核心库，用于Java和JSON数据之间的转换


    /**
     *
     * 1.首先根据起点、终点，去tbl_map_route表查出所有符合条件的路线，根据策略找到线路，直接返回给前端点亮；
     * 2.如果不存在现有路线，那就首先根据起点、终点模糊查找，如果两个结点均存在，那就可能存在线路，继续向下处理；如果有一点不存在，那就一定不能生成对应线路，直接返回警告即可。
     * 3.起点、终点均存在：将数据读入图中，再根据所选策略，使用Dijkstra生成线路，返回给前端。
     * @param begin
     * @param end
     * @param policy
     * @param cargoType
     * @return
     */
    @Override
    public DragonResult getAIRoute(String begin, String end, String policy, String cargoType) {

        int policy1 = Integer.valueOf(policy); // 排序条件1
        // 再随机生成一个数()，作为排序条件2
        Random random = new Random();
        int temp = 0;
        do {
            temp= random.nextInt(3); // 生成一个随机数，范围为 [0, 3)，即 0、1、2,并且不能和策略相等
        } while (temp == policy1);
        int policy2 = temp;
        // 1.首先根据起点、终点，去tbl_map_route表查出所有符合条件的路线，根据策略找到线路，直接返回给前端点亮；
        // 1.1 先根据起点、终点、大件类型，找到所有存在的可行路线。需要注意的是：大件类型是向下兼容的
        List<MapRoute> existAIRoutes = mapRouteMapper.getExistAIRoutes(begin,end,cargoType,policy);
        System.out.println("此时从数据库读到的所有可行路线为："+existAIRoutes.toString());
        if(!existAIRoutes.isEmpty()){
            // 1.2 如果不为空，那就从中挑选出最适合的路线；首先大件类型已经排除过了；
            // 此时注意，要将类的三个属性(routeTime/routeCost/routeLength)转换为float，
            // 再对策略(policy)所对应的的属性(policyName)升序排序；如果策略数据相等()，那就根据另外两个元素(各占一半的权重)排序，
            // 那么此时list中第一个存储的元素，就是我们需要的路线类
            // 创建一个比较器：
            // 返回-1表示前者应该排在后者之前，返回1表示后者应该排在前者之前
            Comparator<MapRoute> comparator = new Comparator<MapRoute>() {
                @Override
                public int compare(MapRoute o1, MapRoute o2) {
                    // 1.获得两个对象的元素值
                    float p1 = o1.getPolicyData(policy1);
                    float p2 = o2.getPolicyData(policy1);
                    if(p1 < p2){
                        return -1;
                    }else if(p1 > p2){
                        return 1;
                    }else{
                        // 如果策略相同，根据另外一个属性的值比较
                        float p3 = o1.getPolicyData(policy2);
                        float p4 = o2.getPolicyData(policy2);
                        if(p3 < p4){
                            return -1;
                        }else if(p3 > p4){
                            return 1;
                        }else{
                            return 0 ;
                        }
                    }
                }
            };
            Collections.sort(existAIRoutes,comparator); // 按照定义的要求排序
            System.out.println("排序后的数据为："+existAIRoutes.toString());
            MapRoute resultRoute = existAIRoutes.get(0); // 获得的目标数据
            System.out.println("此时得到的目标数据为："+resultRoute);
            return DragonResult.ok(resultRoute); // 给前端返回结果数据
        }
        else{
            // 如果不存在现有路线，那就首先根据起点、终点查找，如果两个结点均存在，那就可能存在线路，继续向下处理；如果有一点不存在，那就一定不能生成对应线路，直接返回警告即可。
            // 因此,做以下操作：
            // 1.首先读出所有节点，看请求的两个节点是否均存在；不存在：目前无法生成路线；存在：再向下研究
            List<MapRouteNode> mapRouteNodes = mapRouteNodeMapper.getAllNodes();
            System.out.println("此时搜出来的全部结点为"+mapRouteNodes.toString());
            // 再将所有结点名读入一个list，根据节点名来判断该结点是否存在
            List<String> nodesName = new ArrayList<>();
            for (MapRouteNode m:mapRouteNodes) {
                nodesName.add(m.getName());
            }
            if(!nodesName.contains(begin) || !nodesName.contains(end)){
                // 如果起点和终点有一个不存在，那就直接返回错误信息
                return DragonResult.fail("路线生成所需信息不足,深表歉意！");
            }else{
                // 如果两者均存在,那就使用Dijkstra生成路线喽
                // 1.判断两个节点是否在同一个图中
                Boolean inSameGraph = neo4jController.isInSameGraph(begin, end);
                // 2.如果不在同一张图，那就返回报错
                if(inSameGraph == false){
                    return DragonResult.fail("两点不在同一张图，无法生成智能路线，十分抱歉！");
                }else{
                    // 3.如果在同一张图中，那就生成最短路径
                    // 根据不同的策略，生成不同的string，在Cyper语句中使用
                    Map<String,String > policys = new HashMap<>();
                    policys.put("0","time");
                    policys.put("1","cost");
                    policys.put("2","length");
                    String dijkstraPath = neo4jController.getDijkstraPath(begin, end, policys.get(policy));
                    System.out.println("此时找到的最短路径为："+dijkstraPath.toString());
                    // 将String按split分割，装到一个list中
                    String[] dijkstraNodes = dijkstraPath.split(",");
                    System.out.println("dijkstraNodes为"+dijkstraNodes.toString());

                    // TODO 完成的Dijkstra算法
                    // 1. 又因为最后返回的是一个mapRoute对象，因此我们需要将这条智能线路根据所得到的途经点name，包装成一个途经点list，并且将之转为base64编码
                    //    因此下边这一段生成途经点的Base64编码
                    List<mapRouteNodeVO> nodeList = new ArrayList<>();
                    for (int i = 0; i < dijkstraNodes.length; i++) {
                        MapRouteNode n = mapRouteNodeMapper.getNodeByName(dijkstraNodes[i]);
                        mapRouteNodeVO v = new mapRouteNodeVO();
                        v.setName(n.getName());
                        double lon = Double.parseDouble(n.getLongitude());
                        double lat = Double.parseDouble(n.getLatitude());
                        double[] position = {lon,lat};
                        v.setPosition(position);
                        v.setNote(n.getNote());
                        nodeList.add(v);
                    }
                    System.out.println("得到的途经点数据为："+nodeList.toString());
                    String jsonString = "";
                    // 再将对象数据转为JSOn数据
                    try {
                        ObjectMapper objectMapper = new ObjectMapper();
                        jsonString = objectMapper.writeValueAsString(nodeList);
                    } catch (JsonProcessingException e) {
                        e.printStackTrace();
                    }
                    System.out.println("转换后的Json格式数据为："+jsonString);
                    String paths = encoder.encodeToString(jsonString.getBytes()); // 编码
                    System.out.println("编码后的数据为："+paths);
                    // 路径算是生成结束了

                    // 2.接着开始两两找边，算出这条路径的费用(花费、长度、时间)
                    double costSum = 0;
                    double lengthSum = 0;
                    double timeSum = 0;
                    int truckLimit = 4;
                    for (int i = 0; i < dijkstraNodes.length-1; i++) {
                        String curStart = dijkstraNodes[i];
                        String curEnd = dijkstraNodes[i + 1];
                        // 2.1 找到边，并且计算花费
                        MapRouteEdge e = mapRouteEdgeMapper.getEdgeBy2Name(curStart, curEnd);//根据起点终点找到这条边
                        costSum += Double.parseDouble(e.getEdgeCost());
                        lengthSum += Double.parseDouble(e.getEdgeLength());
                        timeSum += Double.parseDouble(e.getEdgeTime());
                        truckLimit = truckLimit<e.getTruckLimitType()?truckLimit:e.getTruckLimitType();
                    }
                    // 完成

                    // 3.构造路线数据，准备插入
                    MapRoute route = new MapRoute();
                    route.setRouteDistance(lengthSum+"");
                    route.setRouteCost(costSum+"");
                    route.setRouteTime(timeSum+"");
                    route.setIsAdminAdd("0");
                    route.setNote("这是AIRoute自己生成的哦！");
                    route.setBeginName(dijkstraNodes[0]);
                    route.setEndName(dijkstraNodes[dijkstraNodes.length-1]);
                    route.setIsPublic(1);
                    route.setPaths(paths); // 插入路径
                    int existAIRoute = mapRouteMapper.getexistAIroutesNum(); // 找到此时已经有多少条自动生成的路线，从而给他命名
                    existAIRoute++;
                    String curName = "Dijkstra生成"+existAIRoute;
                    route.setName(curName);
                    route.setRoadType("柏油路");
                    route.setSeasons("春、夏、秋、冬");
                    route.setPolicy(policy);
                    route.setTruckLimitType(truckLimit+"");
                    route.setIsAdminAdd(0+"");

                    // 插入map_route表
                    int i = mapRouteMapper.addRoute(route);
                    if(i>0){
                        return DragonResult.ok(route);
                    }else {
                        return DragonResult.fail("Dijkstra生成的路线插入失败！");
                    }
                }
            }
        }
    }










    /**
     *      * 1.将新添的路线加入到tbl_map_route表中
     *      * 2.将各结点信息提取出来，存入到tbl_map_route_node表中
     *      * 3.将各结点之间的路段信息提取出来，存入到tbl_map_route_edge表中
     * @param mapRouteVO
     * @return
     */
    @Override
    public DragonResult addRoute(mapRouteVO mapRouteVO) {
        // 1.用一个工具类来接收前端数据
        System.out.println("接收到的数据："+mapRouteVO);
        // 2.再将其转换为对应的实体类数据MapRoute,并且将各属性补充完成
        MapRoute mapRoute = new MapRoute(mapRouteVO.getName(), mapRouteVO.getArea(), mapRouteVO.getRoad_type(), mapRouteVO.getPolicy(),
                mapRouteVO.getSeasons(), mapRouteVO.getPaths(), mapRouteVO.getNote(), mapRouteVO.getIs_public(), mapRouteVO.getDistance(),
                mapRouteVO.getTime(), mapRouteVO.getCost(),mapRouteVO.getTruckLimitType());
        // 2.1 date_inti就使用当前时间函数来插入吧
        // 2.2 将paths解码
        String pathsDecode = new String(decoder.decode(mapRouteVO.getPaths()));
        System.out.println("解码后的数据为："+pathsDecode);
        // 2.3 再将String中的起点、终点提取出来，存入mapRoute中
        //     那就将该String转换为mapRouteNode数组
        //     使用 Jackson 将 JSON 数据转换为对象数组
        mapRouteNodeVO[] nodeVOS;
        try {
             nodeVOS = objectMapper.readValue(pathsDecode, mapRouteNodeVO[].class);
            // 输出转换后的对象数组
            for (mapRouteNodeVO location : nodeVOS) {
                System.out.println("Name: " + location.getName());
                System.out.println("Position: " + location.getPosition()[0] + ", " + location.getPosition()[1]);
                System.out.println("Note: " + location.getNote());
                System.out.println("Image: " + location.getImg());
                System.out.println();
            }
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
        // 2.4 再将首尾结点提取出来存入
        int len = nodeVOS.length; // 途经点的结点个数
        mapRoute.setBeginName(nodeVOS[0].getName());
        mapRoute.setEndName(nodeVOS[len-1].getName());
        System.out.println("完善后的路线结点"+mapRoute);
        mapRoute.setIsAdminAdd("1"); // 说明是管理员添加的路线
        // 2.5 将该路线插入表中，首先根据路线名寻找，看该路线是否已经存在
        Integer isExist = mapRouteMapper.getIsExist(mapRoute.getName());
        if(isExist == null) isExist = 0; // 如果为空，那就不存在
        if(isExist > 0){
            // 如果存在，那就直接返回错误信息
            return  DragonResult.fail("添加路线失败,该路线已存在！");
        }
        // 如果不存在，那就插入路线
        int addFlag = mapRouteMapper.addRoute(mapRoute);
        System.out.println("插入路线成功："+addFlag);

        // 3.插入节点— 完成
        int  nodeAddFlag = addRouteNode(nodeVOS);
        System.out.println("插入节点成功"+nodeAddFlag);
        if(nodeAddFlag <= 0) return DragonResult.fail("结点插入失败，请重试！");
        // 4.插入边数据-
        int edgeAddFlag = addRouteEdge(mapRouteVO);
        if(edgeAddFlag <= 0) return DragonResult.fail("边插入失败，请重试！");
        // 此时开始向Neo4j更新关系
        neo4jController.insertNeo4jData();

        return DragonResult.ok("路线添加成功！");
    }


    /**
     * 批量插入节点Node
     * @return
     */
    @Override
    public int addRouteNode(mapRouteNodeVO[] nodeVOS){
        // 1.将VO数组转换为装载mapRouteNode的list
        List<MapRouteNode>  list = new ArrayList<>();
        for (int i = 0; i < nodeVOS.length; i++) {
            MapRouteNode node = new MapRouteNode();
            node.setNote(nodeVOS[i].getNote());
            node.setName(nodeVOS[i].getName());
            node.setLongitude(String.valueOf(nodeVOS[i].getPosition()[0])); // 经度
            node.setLatitude(String.valueOf(nodeVOS[i].getPosition()[1])); // 维度
            list.add(node);
        }
        System.out.println("结点转换完毕：");
        for (MapRouteNode n:list) {
            System.out.println(n);
        }

        // 2.然后批量插入即可，并且如果遇到name重复，那就：直接将该行数据更新，而不执行插入操作。
        int addNode = mapRouteMapper.addNode(list);

        return addNode;
    }



    /**
     * 批量插入路线（edge）信息，前端已经处理好了，再处理一下即可
     * @return
     */
    public int addRouteEdge(mapRouteVO mapRouteVO){
        // 后端接收时，先做几点数据处理操作：
        //     4.1 解码，将Base64解码为edge边对象数组
        String decodeEdgesVo = new String(decoder.decode(mapRouteVO.getEdges())); // 获得解码后的数据
        System.out.println("边解码后的String为"+decodeEdgesVo);
        mapRouteEdgeVO[] mapRouteEdgeVOS; // 边的接收数据
        try {
            mapRouteEdgeVOS = objectMapper.readValue(decodeEdgesVo, mapRouteEdgeVO[].class); // 转换成边VO数组
            System.out.println("转换后的边数据为：");
            for (mapRouteEdgeVO s:mapRouteEdgeVOS) {
                // 4.2 将每条边的车辆限制类型填满
                s.setTruckLimitType(Integer.valueOf(mapRouteVO.getTruckLimitType()));
                System.out.println(s.toString());
            }
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
        //     4.3 去重,将解码后相同的边去重掉，
        //        思路：使用一种方法来标识边的唯一性，
        //        例如，将端点按照字典序排列，然后使用一个数据结构来存储已经出现过的端点对。这样，当遍历每个边时，你可以先将端点按照字典序排列，
        //        然后检查是否已经出现过这个端点对，如果出现过，则说明这个边已经存在，可以跳过，否则将这个边添加到结果集中。

        List<mapRouteEdgeVO> uniqueEdges = removeDuplicateEdges(Arrays.asList(mapRouteEdgeVOS) ); // 将边数据存入并且去重
        System.out.println(uniqueEdges);

        //    4.4 将其正式转换为MapRouteEdge对象数组
        int averageCost = Integer.valueOf(mapRouteVO.getCost() )/ uniqueEdges.size(); // 每一段路线的花费，取平均值
        List<MapRouteEdge> list = new ArrayList<>();
        for (int i = 0; i < uniqueEdges.size(); i++) {
            MapRouteEdge node = new MapRouteEdge();
            node.setEdgeCost(String.valueOf(averageCost));  // 路费
            node.setEdgeTime(uniqueEdges.get(i).getEdgeTime()); // 时间
            node.setEdgeLength(uniqueEdges.get(i).getEdgeLength()); // 路长
            node.setTruckLimitType(uniqueEdges.get(i).getTruckLimitType()); // 限制类型
            node.setNode1Name(uniqueEdges.get(i).getNode1Name());
            node.setNode2Name(uniqueEdges.get(i).getNode2Name());
            list.add(node); // 加入数组
        }
        System.out.println("转换后的且即将使用的边数组为："+list);

        // 5. 正式批量插入！！注意要在数据库层面去除重复
        int addEdgeFlag = mapRouteMapper.addEdge(list);
        return addEdgeFlag;
    }
    public static List<mapRouteEdgeVO> removeDuplicateEdges(List<mapRouteEdgeVO> edges) {
        Set<String> seen = new HashSet<>();
        List<mapRouteEdgeVO> uniqueEdges = new ArrayList<>();

        for (mapRouteEdgeVO edge : edges) {
            // 将端点按照字典序排列
            String node1 = edge.getNode1Name();
            String node2 = edge.getNode2Name();
            String key = (node1.compareTo(node2) < 0) ? node1 + "-" + node2 : node2 + "-" + node1;

            // 检查是否已经出现过这个端点对
            if (!seen.contains(key)) {
                seen.add(key);
                uniqueEdges.add(edge);
            }
        }
        return uniqueEdges;
    }
}
